package com.buli.minio.service;


import com.buli.util.file.FileUtils;
import io.minio.*;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.MinioException;
import io.minio.StatObjectResponse;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Service
public class MinioService {

    private static final Logger logger = LoggerFactory.getLogger(MinioService.class);

    @Autowired
    private MinioClient minioClient;

    @Value("${spring.application.minio.bucket-name}")
    private String minioBucket;

    @SneakyThrows
    public String uploadSingleFile(MultipartFile file) {
        return uploadSingleFile(file,  FileUtils.buildObjectName("Minio",FileUtils.extractFileExtension(file.getOriginalFilename()), file.getOriginalFilename()));
    }

    /**
     * 单个文件上传
     *
     * @param file MultipartFile
     * @param filePath 上传文件存储路径
     * @return 文件 URL
     */
    @SneakyThrows
    public String uploadSingleFile(MultipartFile file, String filePath) {
        logger.info("开始上传单个文件: {}", file.getOriginalFilename());

        minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(minioBucket)
                        .object(filePath)
                        .stream(file.getInputStream(), file.getSize(), -1)
                        .contentType(file.getContentType())
                        .build()
        );

        String fileUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                .method(io.minio.http.Method.GET)
                .bucket(minioBucket)
                .object(filePath)
                .build());

        logger.info("单个文件上传成功, 文件名: {}, URL: {}", file.getOriginalFilename(), fileUrl);

        return fileUrl;
    }

    /**
     * 批量文件上传
     *
     * @param files List<MultipartFile>
     * @return 文件 URL 列表
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws MinioException
     */
    @SneakyThrows
    public List<String> uploadMultipleFiles(List<MultipartFile> files) {
        logger.info("开始上传多个文件");

        List<String> fileUrls = files.parallelStream().map(file -> {
            try {
                return uploadSingleFile(file);
            } catch (RuntimeException e) {
                logger.error("文件上传失败: {}", file.getOriginalFilename(), e);
                throw e;
            }
        }).collect(Collectors.toList());

        logger.info("多个文件上传成功");

        return fileUrls;
    }


    /**
     * 单个文件下载
     *
     * @param fileName 文件名
     * @return 文件 URL
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws MinioException
     */
    @SneakyThrows
    public String downloadSingleFile(String fileName){
        logger.info("开始下载单个文件: {}", fileName);

        String fileUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                .method(io.minio.http.Method.GET)
                .bucket(minioBucket)
                .object(fileName)
                .extraQueryParams(Collections.singletonMap("response-content-type", FileUtils.getContentTypeByFileName(fileName))) // 设置 Content-Type
                .build());

        logger.info("单个文件下载成功, 文件名: {}, URL: {}", fileName, fileUrl);

        return fileUrl;
    }

    /**
     * 批量文件下载为 ZIP 压缩包
     *
     * @param fileNames 文件名列表
     * @return ZIP 文件的 URL
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws MinioException
     */
    @SneakyThrows
    public String downloadMultipleFilesAsZip(List<String> fileNames) {
        logger.info("开始下载多个文件并压缩为 ZIP");

        String zipFileName = "files_" + System.currentTimeMillis() + ".zip";
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ZipOutputStream zos = new ZipOutputStream(baos);

        for (String fileName : fileNames) {
            try (InputStream is = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(minioBucket)
                            .object(fileName)
                            .build()
            )) {
                ZipEntry zipEntry = new ZipEntry(fileName);
                zos.putNextEntry(zipEntry);

                byte[] bytes = new byte[1024];
                int length;
                while ((length = is.read(bytes)) >= 0) {
                    zos.write(bytes, 0, length);
                }

                zos.closeEntry();
            } catch (Exception e) {
                logger.error("下载文件失败: {}", fileName, e);
                throw new RuntimeException("下载文件失败", e);
            }
        }

        zos.finish();
        zos.close();

        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());

        minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(minioBucket)
                        .object(zipFileName)
                        .stream(bais, baos.size(), -1)
                        .contentType("application/zip")
                        .build()
        );

        String zipFileUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                .method(io.minio.http.Method.GET)
                .bucket(minioBucket)
                .object(zipFileName)
                .build());

        logger.info("多个文件下载并压缩为 ZIP 成功, 文件名: {}, URL: {}", zipFileName, zipFileUrl);

        return zipFileUrl;
    }

    /**
     * 图片预览接口
     *
     * @param objectName 文件对象名
     * @return 预览 URL
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws MinioException
     */
    @SneakyThrows
    public String getImagePreviewUrl(String objectName) {
        logger.info("开始获取图片预览 URL: {}", objectName);

        String previewUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                .method(io.minio.http.Method.GET)
                .bucket(minioBucket)
                .object(objectName)
                .expiry(3600) // 有效期1小时
                .build());

        logger.info("图片预览 URL 获取成功: {}, URL: {}", objectName, previewUrl);

        return previewUrl;
    }

    /**
     * 通用文件预览接口（支持多种文件类型）
     *
     * @param objectName 文件对象名
     * @return 预览 URL
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws MinioException
     */
    @SneakyThrows
    public String getFilePreviewUrl(String objectName) {
        logger.info("开始获取文件预览 URL: {}", objectName);

        // 获取文件信息以确定文件类型
        StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder()
                .bucket(minioBucket)
                .object(objectName)
                .build());

        String contentType = stat.contentType();
        String previewUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                .method(io.minio.http.Method.GET)
                .bucket(minioBucket)
                .object(objectName)
                .expiry(3600) // 有效期1小时
                .extraQueryParams(Collections.singletonMap("response-content-type", contentType))
                .build());

        logger.info("文件预览 URL 获取成功: {}, Content-Type: {}, URL: {}", objectName, contentType, previewUrl);

        return previewUrl;
    }

    /**
     * 获取文件信息
     *
     * @param objectName 文件对象名
     * @return 文件信息Map
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws MinioException
     */
    @SneakyThrows
    public Map<String, Object> getFileInfo(String objectName) {
        logger.info("开始获取文件信息: {}", objectName);

        StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder()
                .bucket(minioBucket)
                .object(objectName)
                .build());

        Map<String, Object> fileInfo = new HashMap<>();
        fileInfo.put("objectName", objectName);
        fileInfo.put("bucketName", minioBucket);
        fileInfo.put("size", stat.size());
        fileInfo.put("contentType", stat.contentType());
        fileInfo.put("lastModified", stat.lastModified());
        fileInfo.put("etag", stat.etag());
        fileInfo.put("isImage", isImageFile(objectName));
        fileInfo.put("isVideo", isVideoFile(objectName));
        fileInfo.put("isDocument", isDocumentFile(objectName));
        fileInfo.put("canPreview", canPreview(objectName));

        logger.info("文件信息获取成功: {}", fileInfo);

        return fileInfo;
    }

    /**
     * 判断是否为图片文件
     */
    private boolean isImageFile(String objectName) {
        String extension = getFileExtension(objectName).toLowerCase();
        return extension.matches("(jpg|jpeg|png|gif|bmp|webp|svg|ico)");
    }

    /**
     * 判断是否为视频文件
     */
    private boolean isVideoFile(String objectName) {
        String extension = getFileExtension(objectName).toLowerCase();
        return extension.matches("(mp4|avi|mov|wmv|flv|webm|mkv|3gp)");
    }

    /**
     * 判断是否为文档文件
     */
    private boolean isDocumentFile(String objectName) {
        String extension = getFileExtension(objectName).toLowerCase();
        return extension.matches("(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|rtf)");
    }

    /**
     * 判断文件是否支持预览
     */
    private boolean canPreview(String objectName) {
        return isImageFile(objectName) || isVideoFile(objectName) || isDocumentFile(objectName);
    }

    /**
     * 获取文件扩展名
     */
    private String getFileExtension(String fileName) {
        if (fileName == null || !fileName.contains(".")) {
            return "";
        }
        return fileName.substring(fileName.lastIndexOf(".") + 1);
    }

    /**
     * 删除单个文件
     *
     * @param objectName 文件对象名
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws MinioException
     */
    @SneakyThrows
    public void deleteFile(String objectName) throws IOException, NoSuchAlgorithmException, InvalidKeyException, MinioException {
        logger.info("开始删除文件: {}", objectName);

        try {
            // 检查文件是否存在
            minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(minioBucket)
                            .object(objectName)
                            .build()
            );

            // 文件存在，删除文件
            minioClient.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(minioBucket)
                            .object(objectName)
                            .build()
            );

            logger.info("文件删除成功: {}", objectName);
        } catch (ErrorResponseException e) {
            if ("NoSuchKey".equals(e.errorResponse().code())) {
                logger.error("文件不存在: {}", objectName);
                throw new RuntimeException("文件不存在", e);
            } else {
                logger.error("删除文件失败: {}", objectName, e);
                throw new RuntimeException("删除文件失败", e);
            }
        } catch (Exception e) {
            logger.error("删除文件失败: {}", objectName, e);
            throw new RuntimeException("删除文件失败", e);
        }
    }
    /**
     * 批量删除文件
     *
     * @param objectNames 文件对象名列表
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws MinioException
     */
    public void deleteMultipleFiles(List<String> objectNames) throws IOException, NoSuchAlgorithmException, InvalidKeyException, MinioException {
        logger.info("开始批量删除文件: {}", objectNames);

        for (String objectName : objectNames) {
            try {
                deleteFile(objectName);
            } catch (Exception e) {
                logger.error("批量删除文件失败: {}", objectName, e);
                // 可以选择抛出异常或继续处理下一个文件
                throw new RuntimeException("批量删除文件失败", e);
            }
        }

        logger.info("批量删除文件成功: {}", objectNames);
    }

    public String getMinioBucket() {
        return minioBucket;
    }
}
